/*
* Copyright (C) 2008-2009 J-P Nurmi jpnurmi@gmail.com
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* $Id: messageview.cpp 587 2009-05-19 20:19:17Z jpnurmi $
*/

#include "messageview.h"
#include "menu.h"
#include "settings.h"
#include "completer.h"
#include "application.h"
#include "sessionmodelitem.h"
#include "nickhighlighter.h"
#include "completionmodel.h"
#include <QDesktopServices>
#include <QScrollBar>
#include <QTreeView>
#include <QShortcut>
#include <QKeyEvent>
#include <QTime>
#include <QMenu>
#include <ircbuffer.h>

MessageView::MessageView(Irc::Buffer* buffer, QWidget* parent)
    : QWidget(parent)
{
    d.setupUi(this);

    setFocusProxy(d.lineEdit);
    d.textBrowser->installEventFilter(this);
    d.textBrowser->viewport()->installEventFilter(this);
    d.highlighter = new NickHighlighter(d.textBrowser->document());
    connect(d.textBrowser, SIGNAL(anchorClicked(QUrl)), this, SLOT(onAnchorClicked(QUrl)));

    connect(d.lineEdit, SIGNAL(returnPressed()), this, SLOT(onSend()));
    connect(d.pushButton, SIGNAL(clicked()), this, SLOT(onSend()));

    d.completer = new Completer(this);
    d.completer->setWidget(this);
    d.completer->setLineEdit(d.lineEdit);
    d.completer->setCompletionRole(SessionModelItem::NameRole);

    d.findFrame->setTextEdit(d.textBrowser);

    QShortcut* shortcut = new QShortcut(Qt::Key_Tab, this);
    connect(shortcut, SIGNAL(activated()), d.completer, SLOT(onTabPressed()));

    shortcut = new QShortcut(QKeySequence::MoveToPreviousPage, this);
    connect(shortcut, SIGNAL(activated()), this, SLOT(moveToPrevPage()));

    shortcut = new QShortcut(QKeySequence::MoveToNextPage, this);
    connect(shortcut, SIGNAL(activated()), this, SLOT(moveToNextPage()));

    d.buffer = 0;
    setBuffer(buffer);

    d.debugView = 0;
    if (QApplication::arguments().contains("-d"))
    {
        d.debugView = new QTreeView(this);
        d.debugView->setHeaderHidden(true);
        d.gridLayout->addWidget(d.debugView, 0, 1, 1, 3);
    }

    connect(qApp, SIGNAL(settingsChanged()), this, SLOT(applySettings()));
    applySettings();
}

MessageView::~MessageView()
{
}

Irc::Buffer* MessageView::buffer() const
{
    return d.buffer;
}

void MessageView::setBuffer(Irc::Buffer* buffer)
{
    if (d.buffer != buffer)
    {
        if (d.buffer)
        {
            disconnect(d.buffer, SIGNAL(ctcpActionReceived(QByteArray, QByteArray)), this, SLOT(ctcpActionReceived(QByteArray, QByteArray)));
            disconnect(d.buffer, SIGNAL(ctcpReplyReceived(QByteArray, QByteArray)), this, SLOT(ctcpReplyReceived(QByteArray, QByteArray)));
            disconnect(d.buffer, SIGNAL(ctcpRequestReceived(QByteArray, QByteArray)), this, SLOT(ctcpRequestReceived(QByteArray, QByteArray)));
            disconnect(d.buffer, SIGNAL(messageReceived(QByteArray, QByteArray)), this, SLOT(messageReceived(QByteArray, QByteArray)));
            disconnect(d.buffer, SIGNAL(noticeReceived(QByteArray, QByteArray)), this, SLOT(noticeReceived(QByteArray, QByteArray)));
            disconnect(d.buffer, SIGNAL(numericMessageReceived(QByteArray, uint, QByteArray)), this, SLOT(numericMessageReceived(QByteArray, uint, QByteArray)));
        }

        if (buffer)
        {
            connect(buffer, SIGNAL(ctcpActionReceived(QByteArray, QByteArray)), this, SLOT(ctcpActionReceived(QByteArray, QByteArray)));
            connect(buffer, SIGNAL(ctcpReplyReceived(QByteArray, QByteArray)), this, SLOT(ctcpReplyReceived(QByteArray, QByteArray)));
            connect(buffer, SIGNAL(ctcpRequestReceived(QByteArray, QByteArray)), this, SLOT(ctcpRequestReceived(QByteArray, QByteArray)));
            connect(buffer, SIGNAL(messageReceived(QByteArray, QByteArray)), this, SLOT(messageReceived(QByteArray, QByteArray)));
            connect(buffer, SIGNAL(noticeReceived(QByteArray, QByteArray)), this, SLOT(noticeReceived(QByteArray, QByteArray)));
            connect(buffer, SIGNAL(numericMessageReceived(QByteArray, uint, QByteArray)), this, SLOT(numericMessageReceived(QByteArray, uint, QByteArray)));
        }

        d.buffer = buffer;
    }
}

CompletionModel* MessageView::completionModel() const
{
    return qobject_cast<CompletionModel*>(d.completer->model());
}

void MessageView::setCompletionModel(CompletionModel* model)
{
    d.completer->setModel(model);
    if (d.debugView) {
        d.debugView->setModel(model->completionModel());
    }
}

QString MessageView::senderHtml(const QString& sender) const
{
    if (sender.isEmpty() || sender.startsWith("#") || sender.startsWith("&"))
        return sender;

    return QString("<a href='query:%1'>%1</a>").arg(sender);
}

void MessageView::receiveMessage(const QString& format, const QStringList& params, bool highlight)
{
    QString message = format;
    foreach (const QString& param, params)
        message = message.arg(param);

    const QString time = QTime::currentTime().toString();
    message = tr("[%1] %2").arg(time).arg(message);
    message = QString("<span style='color:%2'>%1</span>").arg(message);

    append(message, highlight);
}

void MessageView::clear()
{
    d.textBrowser->clear();
}

bool MessageView::eventFilter(QObject* receiver, QEvent* event)
{
    Q_UNUSED(receiver);
    if (event->type() == QEvent::KeyPress)
    {
        QKeyEvent* keyEvent =  static_cast<QKeyEvent*>(event);
        // for example:
        // - Ctrl+C goes to the browser
        // - Ctrl+V goes to the line edit
        // - Shift+7 ("/") goes to the line edit
        switch (keyEvent->key())
        {
        case Qt::Key_Shift:
        case Qt::Key_Control:
        case Qt::Key_Meta:
        case Qt::Key_Alt:
        case Qt::Key_AltGr:
            break;
        default:
            if (!keyEvent->matches(QKeySequence::Copy))
            {
                QApplication::sendEvent(d.lineEdit, keyEvent);
                d.lineEdit->setFocus();
                return true;
            }
            break;
        }
    }
    else if (event->type() == QEvent::ContextMenu)
    {
        QContextMenuEvent* contextMenuEvent = static_cast<QContextMenuEvent*>(event);
        QString anchor = d.textBrowser->anchorAt(contextMenuEvent->pos());
        if (anchor.startsWith("query:"))
        {
            QString nick = QUrl(anchor).path();
            Menu* menu = Menu::createNickContextMenu(nick, d.buffer->name(), this);
            connect(menu, SIGNAL(command(QString)), this, SIGNAL(command(QString)));
            menu->exec(contextMenuEvent->globalPos());
            delete menu;
            return true;
        }
    }
    return false;
}

void MessageView::ctcpActionReceived(const QByteArray& prefix, const QByteArray& action)
{
    /*
    QString target = sessio->resolveTarget(origin, receiver);
    prepareView(target);

    bool alert = action.contains(sessio->nick());
    MessageView* view = handler.view(target);
    if (alert)
        setTabAlert(indexOf(view), true);

    QStringList params;
    params << view->senderHtml(origin) << Irc::Util::messageToHtml(action);
    view->receiveMessage(tr("* %1 %2"), params, alert);
    */

    //receiveMessage(tr("* %1 %2").arg(QString::fromUtf8(prefix)).arg(QString::fromUtf8(action)), alert);
    receiveMessage(tr("* %1 %2"), QStringList() << QString::fromUtf8(prefix) << QString::fromUtf8(action));
}

void MessageView::ctcpReplyReceived(const QByteArray& prefix, const QByteArray& reply)
{
}

void MessageView::ctcpRequestReceived(const QByteArray& prefix, const QByteArray& request)
{
}

void MessageView::messageReceived(const QByteArray& prefix, const QByteArray& message)
{
    /*
    QString target = sessio->resolveTarget(origin, receiver);
    prepareView(target);

    bool alert = message.contains(sessio->nick());
    bool privmsg = !target.startsWith('#') && !target.startsWith('&');
    MessageView* view = handler.view(target);
    if ((alert || privmsg) && (currentWidget() != view || !isVisible()))
        setTabAlert(indexOf(view), true);

    QStringList params;
    params << view->senderHtml(origin) << Irc::Util::messageToHtml(message);
    view->receiveMessage(tr("&lt;%1&gt; %2"), params, alert);
    */

    //receiveMessage(tr("&lt;%1&gt; %2").arg(QString::fromUtf8(prefix)).arg(QString::fromUtf8(message)), alert);
    receiveMessage(tr("&lt;%1&gt; %2"), QStringList() << QString::fromUtf8(prefix) << QString::fromUtf8(message));
}

void MessageView::noticeReceived(const QByteArray& prefix, const QByteArray& notice)
{
    /*
    QString target = sessio->resolveTarget(origin, receiver);
    prepareView(target);

    bool alert = message.contains(sessio->nick());
    MessageView* view = handler.view(target);
    if (alert)
        setTabAlert(indexOf(view), true);

    QStringList params;
    params << view->senderHtml(origin) << Irc::Util::messageToHtml(message);
    view->receiveMessage(tr("[%1] %2"), params, alert);
    */
    
    //receiveMessage(tr("[%1] %2").arg(QString::fromUtf8(prefix)).arg(QString::fromUtf8(notice)), alert);
    receiveMessage(tr("[%1] %2"), QStringList() << QString::fromUtf8(prefix) << QString::fromUtf8(notice));
}

void MessageView::numericMessageReceived(const QByteArray& prefix, uint code, const QByteArray& message)
{
}

void MessageView::onSend()
{
    QString input = d.lineEdit->text();
    if (!input.isEmpty())
    {
        d.lineEdit->clear();
        QApplication::processEvents();
        emit send(input);
    }
}

void MessageView::moveToNextPage()
{
    QScrollBar* scrollBar = d.textBrowser->verticalScrollBar();
    scrollBar->triggerAction(QScrollBar::SliderPageStepAdd);
}

void MessageView::moveToPrevPage()
{
    QScrollBar* scrollBar = d.textBrowser->verticalScrollBar();
    scrollBar->triggerAction(QScrollBar::SliderPageStepSub);
}

void MessageView::onAnchorClicked(const QUrl& link)
{
    if (link.scheme() == "query")
        emit query(link.path());
    else
        QDesktopServices::openUrl(link);
}

void MessageView::applySettings()
{
    Settings settings = Application::settings();
    d.textBrowser->setFont(settings.font);

    QString messageColor = settings.colors.value(Settings::Message);
    QString backgroundColor = settings.colors.value(Settings::Background);
    d.textBrowser->setStyleSheet(QString("QTextBrowser { background-color: %1 }").arg(backgroundColor));
    d.lineEdit->setStyleSheet(QString("QLineEdit { background-color: %1; color: %2 }").arg(backgroundColor).arg(messageColor));

    QRegExp regExp("<span style=\"(?:\\s*)color:(?:\\s*)[^\"]+;(?:\\s*)\">");
    QString html = d.textBrowser->toHtml();
    d.textBrowser->clear();
   
    QStringList lines = html.split("\n");
    foreach (QString line, lines)
    {
        int pos = 0;
        bool found = false;
        while ((pos = regExp.indexIn(line, pos)) != -1)
        {
            found = true;
            QString after = QLatin1String("<span style='color:%1'>");
            line.replace(pos, regExp.matchedLength(), after);
        }
        if (found)
            append(line);
    }
}

void MessageView::append(const QString& message, bool highlight)
{
    Settings::ColorType color = Settings::Message;
    if (highlight)
        color = Settings::Highlight;
    else if (QRegExp("(<(span|p) [^>]*>)*\\[\\d+:\\d+:\\d+\\] ! .*").exactMatch(message))
        color = Settings::Event;
    else if (QRegExp("(<(span|p) [^>]*>)*\\[\\d+:\\d+:\\d+\\] \\? .*").exactMatch(message))
        color = Settings::Notice;
    else if (QRegExp("(<(span|p) [^>]*>)*\\[\\d+:\\d+:\\d+\\] \\[.*").exactMatch(message))
        color = Settings::Notice;
    else if (QRegExp("(<(span|p) [^>]*>)*\\[\\d+:\\d+:\\d+\\] \\* .*").exactMatch(message))
        color = Settings::Action;

    d.textBrowser->append(message.arg(Application::settings().colors.value(color)));
}
